package pers.richard.ormybatis.dao;

import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.annotation.PostConstruct;

import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionTemplate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.annotation.AnnotationUtils;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import pers.richard.ormybatis.annotation.DaoConfig;
import pers.richard.ormybatis.bean.AbstractDomain;
import pers.richard.ormybatis.bean.field.FieldName;
import pers.richard.ormybatis.bean.field.IField;
import pers.richard.ormybatis.bean.mapping.DeleteMappingBean;
import pers.richard.ormybatis.bean.mapping.InsertBatchMappingBean;
import pers.richard.ormybatis.bean.mapping.InsertMappingBean;
import pers.richard.ormybatis.bean.mapping.SelectFieldMappingBean;
import pers.richard.ormybatis.bean.mapping.SelectMappingBean;
import pers.richard.ormybatis.bean.mapping.UpdateMappingBean;
import pers.richard.ormybatis.bean.sql.Update;
import pers.richard.ormybatis.bean.sql.Where;
import pers.richard.ormybatis.util.CaseFormatUtil;

/**
 *	提供了基本CRUD方法,
 * @param <T> 数据库对应的PO bean类
 */
public class OrmybatisBaseDao<T extends AbstractDomain> implements ApplicationContextAware {
	protected final Logger log = LoggerFactory.getLogger(getClass());
	protected ApplicationContext applicationContext = null;

	/**数据源 */
	private SqlSessionTemplate sqlSessionTemplate;
	
	/** 泛型domain类型 */
	@SuppressWarnings("unchecked")
	private Class<T> poClass = (Class<T>)ClassUtil.getTypeArgument(this.getClass());

	private final DaoConfig daoConfigAnnotation = AnnotationUtils.findAnnotation(this.getClass(), DaoConfig.class);
	
	/** 表的主键的数据库字段名 */
	private FieldName pkFieldName = null;
	
	/** 表名 */
	private String tableName = null;
	
	/**
	 *	在spring初始化按子类的@DaoConfig注解配置信息注入相关属性值
	 */
	@PostConstruct
	public void init() {
		if (daoConfigAnnotation.lowerUnderscore()) {
			this.pkFieldName = new FieldName(CaseFormatUtil.toLowerUnderscore(daoConfigAnnotation.id()));
		} else {
			this.pkFieldName = new FieldName(daoConfigAnnotation.id());
		}
		this.tableName = StrUtil.blankToDefault(daoConfigAnnotation.table(), this.getTableName());
		//配置sqlSessionFactory
		String sqlSessionFactoryBeanName = daoConfigAnnotation.sqlSessionFactoryBeanName();
		SqlSessionFactory sqlSessionFactory = null;
		if (StrUtil.isBlank(sqlSessionFactoryBeanName)) {
			sqlSessionFactory = applicationContext.getBean(SqlSessionFactory.class);
		} else {
			sqlSessionFactory = applicationContext.getBean(sqlSessionFactoryBeanName, SqlSessionFactory.class);
		}
		if (sqlSessionFactory == null) {
			throw new BeanCreationException("没有找到org.mybatis.spring.SqlSessionFactory");
		}
		this.sqlSessionTemplate = new SqlSessionTemplate(sqlSessionFactory);
	}

	/**
	 *	按id主键查询
	 * @param id主键
	 * @return
	 */
	public T selectOne(Number id) {
		Where where = Where.create(pkFieldName, id);
		return this.selectOne(where);
	}

	/**
	 *	查询单条记录按map拼查询条件
	 * <pre>
	 * Map map = (K,V),(k1,v1)
	 * "WHERE K = V AND k1 = v1"
	 * </pre>
	 * @param paramMap	查询条件K是bean属性名或DB字段名,V为值
	 * @return
	 */
	public T selectOne(Map<String, Object> paramMap) {
		Where where = Where.create().eq(paramMap).orderDesc(this.pkFieldName);
		return this.selectOne(where);
	}

	/**
	 *	查询单条记录按map拼查询条件
	 * @param w 查询构建builder对象
	 * @return
	 */
	public T selectOne(Where w) {
		w.limit(1);
		List<T> result = this.selectList(w);
		if (result == null || result.size() == 0) {
			return null;
		}
		return CollUtil.get(result, 0);
	}

	/**
	 *	按多个id主键查询
	 * @param ids  主键集合
	 * @return
	 */
	public List<T> selectList(List ids) {
		Where where = Where.create().in(this.pkFieldName, ids);
		return this.selectList(where);
	}

	/**
	 *	查询单条记录按map拼查询条件
	 * <pre>
	 *  Map map = (K,V),(k1,v1)
	 * "WHERE K = V AND k1 = v1"
	 * </pre>
	 * @param paramMap
	 * @return
	 */
	public List<T> selectList(Map<String, Object> paramMap) {
		Where where = Where.create().eq(paramMap);
		return this.selectList(where);
	}

	/**
	 *	查询多条记录
	 * @param w 查询构建builder对象
	 * @return
	 */
	public List<T> selectList(Where w) {
		if (w.isEmptyOperExpressList()) {
			log.warn("[{}]表没有查询条件", this.tableName);
			return Collections.emptyList();
		}
		String statement = this.getBaseSqlStatement("selecListByWhere");
		String table = this.daoConfigAnnotation.table();
		boolean lowerUnderscore = this.daoConfigAnnotation.lowerUnderscore();
		SelectMappingBean selectMappingParamBean = new SelectMappingBean(table, this.pkFieldName.toString(), w, poClass, lowerUnderscore);
		List<Map<String, Object>> mapList = this.sqlSessionTemplate.selectList(statement, selectMappingParamBean);
		List<T> list = this.toPoList(mapList);
		return list;
	}

	/**
	 *	按dao方法名mapper匹配sql statement id
	 * @param parameter  查询条件
	 * @return
	 */
	public <E> E selectOneMapperStatementByMethodName(Object parameter) {
		StackTraceElement daoSubClassStackTraceElement = Thread.currentThread().getStackTrace()[2];
		String daoSubClassMethodName = daoSubClassStackTraceElement.getMethodName();
		return this.selectOne(daoSubClassMethodName, parameter);
	}

	/**
	 *	按dao方法名mapper匹配sql statement id
	 * @param parameter 查询条件
	 * @return
	 */
	public <E> List<E> selectListMapperStatementByMethodName(Object parameter) {
		String daoSubClassMethod = Thread.currentThread().getStackTrace()[2].getMethodName();
		return this.selectList(daoSubClassMethod, parameter);
	}

	/**
	 *	查询单个字段
	 * @param fieldName  查询数据库的字段名
	 * @param w  查询构建builder对象
	 * @return 返回记录集合
	 */
	public <E> E selectOneByField(IField Field, Where w) {
		List<E> list = this.selectListByField(Field, w);
		return CollUtil.get(list, 0);
	}

	/**
	 *	查询单个字段的集合
	 * @param fieldName 查询数据库的字段名
	 * @param w 查询构建builder对象
	 * @return 返回记录集合
	 */
	@SuppressWarnings("unchecked")
	public <E> List<E> selectListByField(IField Field, Where w) {
		String statement = this.getBaseSqlStatement("selecFieldByWhere");
		String table = this.daoConfigAnnotation.table();
		boolean lowerUnderscore = this.daoConfigAnnotation.lowerUnderscore();
		SelectFieldMappingBean mappingParamBean = new SelectFieldMappingBean(table, this.pkFieldName.toString(), Field.toString(), w, this.poClass, lowerUnderscore);
		List<E> result = this.sqlSessionTemplate.selectList(statement, mappingParamBean);
		String fieldName = CaseFormatUtil.toLowerCamel(Field.toString());
		if (!ReflectUtil.hasField(poClass, fieldName)) {
			return result;
		}
		//转换为po类中属性类型
		Class fieldClz = BeanUtil.getPropertyDescriptor(poClass, fieldName).getPropertyType();
		return Convert.toList(fieldClz, result);
	}

	/**
	 *	查询记录数
	 * @param paramMap
	 * @return
	 */
	public int count(Map<String, Object> paramMap) {
		Where where = Where.create().eq(paramMap);
		return this.count(where);
	}

	/**
	 *	查询记录数
	 * @param w 查询构建builder对象
	 * @return
	 */
	public Integer count(Where w) {
		w.limit(1);//强制limit 1 防止不合理limt sql
		String statement = this.getBaseSqlStatement("countByWhere");
		String table = this.daoConfigAnnotation.table();
		boolean lowerUnderscore = this.daoConfigAnnotation.lowerUnderscore();
		SelectMappingBean selectMappingBean = new SelectMappingBean(table, this.pkFieldName.toString(), w, poClass, lowerUnderscore);
		return this.sqlSessionTemplate.selectOne(statement, selectMappingBean);
	}

	
	/**
	 *	求和
	 * @param Fieldw 查询字段名
	 * @param w 查询构建where查询对象
	 * @return
	 */
	@SuppressWarnings("unchecked") 
	public <E> E sum(IField Field, Where w) {
		String statement = this.getBaseSqlStatement("sum");
		boolean lowerUnderscore = this.daoConfigAnnotation.lowerUnderscore();
		SelectFieldMappingBean fieldMappingBean = new SelectFieldMappingBean(this.tableName, this.pkFieldName.toString(), Field.toString(), w, this.poClass, lowerUnderscore);
		E sum = this.sqlSessionTemplate.selectOne(statement, fieldMappingBean);
		String fieldName = CaseFormatUtil.toLowerCamel(Field.toString());
		if (!ReflectUtil.hasField(poClass, fieldName)) {
			return sum;
		}
		//转换为po类中属性类型
		Class fieldType = BeanUtil.getPropertyDescriptor(poClass, fieldName).getPropertyType();
		return (E)Convert.convert(fieldType, sum);
	}
	
	/**
	 *	单条插入
	 * @param po 持久化对象
	 * @return 插入记录数
	 */
	public Integer insert(T po) {
		String statement = this.getBaseSqlStatement("insert");
		String table = this.daoConfigAnnotation.table();
		boolean lowerUnderscore = this.daoConfigAnnotation.lowerUnderscore();
		InsertMappingBean insertMappingParamBean = new InsertMappingBean(table, this.pkFieldName.toString(), po, lowerUnderscore);
		int num = this.sqlSessionTemplate.insert(statement, insertMappingParamBean);
		Object insertId = insertMappingParamBean.getInsertId();
		if (ObjectUtil.isNotEmpty(insertId)) {
			String id =  pkFieldName.toString();
			if (lowerUnderscore) {
				id = CaseFormatUtil.toLowerCamel(pkFieldName.toString());
			}
			BeanUtil.setProperty(po, id , insertId);
		}
		return num;
	}

	/**
	 *	批量插入(默认20条记录指提交一次)
	 * @param poList 持久化对象集合
	 * @return 插入记录数
	 */
	@SuppressWarnings("unchecked")
	public int insertBatch(T... po) {
		return this.insertBatch(Arrays.asList(po));
	}

	/**
	 *	批量插入(默认20条记录指提交一次)
	 * @param poList 持久化对象集合
	 * @return 插入记录数
	 */
	public int insertBatch(List<T> poList) {
		return this.insertBatch(poList, 20);
		
	}
	
	/**
	 *	批量插入
	 * @param poList 持久化对象集合
	 * @param batchNum 一次指提交记录数
	 */
	public int insertBatch(List<T> poList, int batchNum) {
		int num = 0;
		String table = this.daoConfigAnnotation.table();
		String statement = this.getBaseSqlStatement("insertBatch");
		if (ObjectUtil.isNotEmpty(poList)) {
			for (int i = 0; i <= poList.size() / batchNum; i++) {
				int fromIndex = i * batchNum;
				int toIndex = fromIndex + batchNum;
				List<T> subList = CollUtil.sub(poList, fromIndex, toIndex);
				if (ObjectUtil.isNotEmpty(subList)) {
					InsertBatchMappingBean insertBatchMappingBean = new InsertBatchMappingBean(table, this.pkFieldName.toString(), subList, daoConfigAnnotation.lowerUnderscore());
					num += this.sqlSessionTemplate.insert(statement, insertBatchMappingBean);
				}
			}
		}
		return num;
	}

	/**
	 *	按po id主键查询有则update,无则insert
	 * @param po 持久化对象
	 * @return
	 */
	public int insertOrUpdate(T po) {
		Object id = BeanUtil.getProperty(po, pkFieldName.toString());
		if (ObjectUtil.isEmpty(id)) {
			return this.insert(po);
		}
		Where wb = Where.create(pkFieldName, id);
		return this.insertOrUpdate(po, wb);
	}

	/**
	 *	按where查询条件查询有则update,无则Insert
	 * @param po  持久化对象
	 * @param where  查询条件构造器
	 * @return
	 */
	public int insertOrUpdate(T po, Where w) {
		T selectPo = this.selectOne(w);
		if (selectPo == null) {
			return this.insert(po);
		} else {
			Object id = BeanUtil.getProperty(selectPo, pkFieldName.toString());
			BeanUtil.setProperty(po, pkFieldName.toString(), id);
			return this.update(po);
		}
	}

	/**
	 *	按po的id主键查找修改po,为空的属性不修改
	 * @param po
	 * @return 修改记录数
	 */
	public int update(T po) {
		Map<String, Object> updateFieldMap =  daoConfigAnnotation.lowerUnderscore() ? po.toMapKeyToUnderscore() : po.toMap();
		Object id = updateFieldMap.get(pkFieldName.toString());
		if (ObjectUtil.isEmpty(id)) {
			throw new RuntimeException("主键值不能为null");
		}
		updateFieldMap.remove(id);
		Update update = Update.create().updateEq(updateFieldMap).whereEq(this.pkFieldName, id);
		return this.update(update);
	}

	/**
	 *	通过参数构造器来update记录
	 * @param ub 参数构造器
	 * @return 修改记录数量
	 */
	public int update(Update up) {
		if (up.getWhere().getOperExpressList().isEmpty() || up.getUpdateField().isEmpty()) {
			return 0;
		}
		String statement = this.getBaseSqlStatement("update");
		UpdateMappingBean umBean = new UpdateMappingBean(this.tableName, this.pkFieldName.toString(), up, poClass, this.daoConfigAnnotation.lowerUnderscore());
		return this.sqlSessionTemplate.update(statement, umBean);
	}
	
	/**
	 *	批量修改(默认20条提交)
	 * @param poList
	 * @return 成功修改记录数量
	 */
	public void updateBatch(List<T> poList) {
		this.updateBatch(poList, 20);
	}
	
	/**
	 *	批量修改
	 * @param poList
	 * @return 成功修改记录数量
	 */
	public void updateBatch(List<T> poList, int batchNum) {
		if (CollUtil.isEmpty(poList)) {
			return;
		}
		String table = this.daoConfigAnnotation.table();
		String primaryKeyName = this.daoConfigAnnotation.id();
		try (SqlSession sqlSession = this.sqlSessionTemplate.getSqlSessionFactory().openSession(ExecutorType.BATCH, false);) {
			for (int i = 0; i <= poList.size() / batchNum; i++) {
				int fromIndex = i * batchNum;
				int toIndex = fromIndex + batchNum;
				List<T> subList = CollUtil.sub(poList, fromIndex, toIndex);
				if (CollUtil.isNotEmpty(subList)) {
					for (T t : subList) {
						Map<String, Object> updateFieldMap = t.toMapKeyToUnderscore();
						Object id = updateFieldMap.get(this.pkFieldName.toString());
						updateFieldMap.remove(this.pkFieldName.getName());//不更新主键
						Update up = Update.create().updateEq(updateFieldMap).whereEq(this.pkFieldName, id);
						String statement = this.getBaseSqlStatement("update");
						UpdateMappingBean umBean = new UpdateMappingBean(table, primaryKeyName, up, poClass , this.daoConfigAnnotation.lowerUnderscore());
						sqlSession.update(statement, umBean);
					}
					sqlSession.commit();
					sqlSession.clearCache(); 
				}
			}
		}
	}

	/**
	 *	批量删除
	 * @param ids
	 * @return 删除记录数
	 */
	public int delete(Number... ids) {
		Where where = Where.create().in(this.pkFieldName, Arrays.asList(ids));
		return this.delete(where);
	}

	/**
	 *	批量删除
	 * @param w where条件
	 * @return 删除记录数
	 */
	public int delete(Where w) {
		// 如果where为空,不修改
		if (w.isEmptyOperExpressList()) {
			return 0;
		}
		String table = this.daoConfigAnnotation.table();
		String statement = this.getBaseSqlStatement("delete");
		DeleteMappingBean deleteMappingBean = new DeleteMappingBean(table, this.pkFieldName.toString(), w);
		return this.sqlSessionTemplate.delete(statement, deleteMappingBean);
	}

	public <E> E selectOne(String statement, Object parameter) {
		return this.sqlSessionTemplate.selectOne(this.getStatement(statement), parameter);
	}

	public <E> List<E> selectList(String statement, Object parameter) {
		return this.sqlSessionTemplate.selectList(this.getStatement(statement), parameter);
	}

	public int insert(String statement, Object parameter) {
		return this.sqlSessionTemplate.insert(this.getStatement(statement), parameter);
	}

	public int delete(String statement, Object parameter) {
		return this.update(statement, parameter);
	}

	public int update(String statement, Object parameter) {
		return this.sqlSessionTemplate.update(this.getStatement(statement), parameter);
	}

	public <E> E selectOneStatement(String statement, Object parameter) {
		List<E> list = this.selectListStatement(statement, parameter);
		return CollUtil.get(list, 0);
	}

	public <E> List<E> selectListStatement(String statement, Object parameter) {
		return this.sqlSessionTemplate.selectList(this.getStatement(statement), parameter);
	}
	
	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		this.applicationContext = applicationContext;
	}

	/**
	 *	得到mapper的namespace
	 * @param statement SQL语句的id标识
	 * @return 全路径名的statement
	 */
	public String getStatement(String statement) {
		String namespace = daoConfigAnnotation.namespace();
		if (StrUtil.isBlank(namespace)) {
			String simpleClassName = this.getClass().getSimpleName();
			namespace = StrUtil.removeSufAndLowerFirst(simpleClassName, "Dao");
		}
		return String.join(".", namespace, statement);
	}

	/**
	 *	得到ormybaits_sql.xml namespace
	 * @param statement SQL语句的id标识
	 * @return 全路径名的statement
	 */
	private String getBaseSqlStatement(String statement) {
		return  "ormybatis." + statement;
	}

	/**
	 *	得到当前操作表的mybatis的namespace
	 */
	private String getTableName() {
		String name = StrUtil.removeSuffix(this.getClass().getSimpleName(), "Dao");
		return CaseFormatUtil.toLowerUnderscore(name);
	}
	
	/**
	 * map转为泛型类T po
	 */
	@SuppressWarnings("unchecked")
	private List<T> toPoList(List<Map<String, Object>> mapList) {
		List<T> result = null;
		try {
			result = mapList.getClass().newInstance();
			BeanUtil.copyProperties(mapList, result);
			result.clear();
		} catch (Exception e) {
			log.error(null, e);
		}
		for (Map<String, Object> map : mapList) {
			T po = this.toPo(map);
			result.add(po);
		}
		return result;
	}
	
	/**
	 *	map转为泛型类T po
	 */
	private T toPo(Map<String, Object> map) {
		// map key变成驼峰命名法
		Map<String, Object> camelKeyMap = new HashMap<>();
		for (String key : map.keySet()) {
			Object value = map.get(key);
			String camelKey = CaseFormatUtil.toLowerCamel(key);
			if (value == null) {
				continue;
			}
			camelKeyMap.put(camelKey, value);
		}
		T po = BeanUtil.toBean(camelKeyMap, poClass);
		return po;
	}
	
	public SqlSessionTemplate getSqlSessionTemplate() {
		return sqlSessionTemplate;
	}
}